extends KinematicBody


#physics
var max_jump_height : float = 1.2
var jump_sensibility : float = 1.5
var gravity : int  = -750
var speed : int = 300
var ball_rotation : int = -10

var gravity_reset : int = -750

var direction : Vector3 = Vector3.LEFT
var velocity : Vector3  = Vector3(0,0,0)
var is_jump : bool = false
var is_boost_mode : bool = false

var squeezed : bool = false
var level_completed : bool = false

var main_camera : Camera

##smooth jump calculations
var latest_height : float = 1.4
var middle_jump : bool = true

#nodes
onready var globalGame = get_tree().get_current_scene()
onready var sounds = globalGame.get_node("Sounds")
onready var animation : AnimationPlayer = $AnimationPlayer
onready var area : Area = $Area
onready var ball : Spatial = $Ball
onready var jump : Tween = $jump
onready var forward : Tween = $forward
onready var boost : Tween = $boost
onready var left_ray : RayCast = $side_rays/left_ray
onready var right_ray : RayCast = $side_rays/right_ray
onready var bottom_ray : RayCast = $side_rays/bottom
onready var fake_shadow : SpotLight = $fake_shadow
onready var current_level = get_parent()


func _ready():
	yield(current_level,"ready") # wait for level to load to get stairs count 

	globalGame.connect("on_boost",self,"on_boost_emitted")
	main_camera = get_parent().get_node("main_camera")
	## set ball sensibility 
	bottom_ray.cast_to = Vector3(0,-jump_sensibility,0)
	set_skin(B4DFramework.get_skin())
	restore_checkpoint()


func _physics_process(delta):
	if is_jump:
		if forward.is_active():
			yield(forward,"tween_all_completed")
		is_jump = false
		latest_height += 1
#		if not squeezed:
#			animation.play("squeeze_ball_up")
#			squeezed = true
		
		gravity = 0
		is_side_jump()
		jump() #middle_jump
		forward()

	if not is_on_floor():
		velocity.y = gravity * delta
	else:
		if squeezed:
			squeezed = false

	velocity.x = speed * direction.x * delta  # side movements
	ball.rotation.z += ball_rotation * direction.x * delta # ball rotation
	
	move_and_slide(velocity, Vector3.UP)
	if translation.y <= latest_height - 4: # 6 stairs down
		velocity.y = 0 
		speed = 0
		gravity = -300
	if translation.y <= latest_height - 10: # 6 stairs down
		die()


func on_body_entered(body : Node):
	if body.is_in_group("normal_side"):
		switch_direction()
		var sides_anim : AnimationPlayer = body.get_node_or_null("AnimationPlayer")
		if sides_anim != null:
			if not sides_anim.is_playing() :
				if sides_anim.has_animation("side_explosion"):
					sounds.side_explosive_touch_play()
					sides_anim.play("side_explosion")
				else : 
					sounds.side_wall_touch_play()
					sides_anim.play("side_bounce")
			else: #normal side moving
				sounds.side_wall_touch_play()
		else:#No bounce animation
			sounds.side_wall_touch_play()

	if body.is_in_group("explosive_stair"):
		var explosion_animation : AnimationPlayer = body.get_node("AnimationPlayer")
		if not explosion_animation.is_playing() :
			explosion_animation.play("stair_explosion")
	if body.is_in_group("spikes"):
		die()


func switch_direction():
	if direction == Vector3.RIGHT:
		direction = Vector3.LEFT
	else:
		direction = Vector3.RIGHT


func _unhandled_input(event):
	var touch : bool = event is InputEventScreenTouch and event.is_pressed()
	var grounded_or_middle : bool  = is_on_floor() or bottom_ray.is_colliding()
	
	if touch and grounded_or_middle: #is_on_floor()
		sounds.jump_play()
		is_jump = true


func die():
	if not level_completed: #Block repetitive call to this
		sounds.game_over_play()
		level_completed = true
		jump.stop_all()
		forward.stop_all()
		speed = 0
		ball.visible = false
		fake_shadow.visible = false
		
		var explosion_particle = preload("res://scenes/utils/explosion_particle.tscn")
		var explosion = explosion_particle.instance()
		add_child(explosion)
		var explosion_animation : AnimationPlayer = explosion.get_node("AnimationPlayer")
		explosion_animation.play("ball_explosion")
		yield(explosion_animation,"animation_finished")
		globalGame.level_completed(false)


func jump(boost_mode : bool = false , boost_stair_nbr : float = 0):
	var current = translation.y
	var final = current + calculate_jump_distance()
	if boost_mode:
		final += 1.7 # make the ball a little higher then the next stair 
		latest_height += boost_stair_nbr
	jump.interpolate_property(self,
	"translation:y",
	current,
	final,
	0.2,
	Tween.TRANS_SINE,
	Tween.EASE_IN_OUT)
	jump.start()
	focus_on_player()

func forward():
	var current = translation.z
	var final = current - 1
	forward.interpolate_property(self,
	"translation:z",
	current,
	final,
	0.1,
	Tween.TRANS_LINEAR,
	Tween.EASE_IN_OUT)
	
	forward.start()

func calculate_jump_distance() -> float:
	var current_height : float = translation.y
	return latest_height + max_jump_height - current_height

func on_forward_completed():
	if is_boost_mode:
		return
	gravity = gravity_reset

func on_area_entered(area : Area):
	if area.is_in_group("final") and not level_completed:
		set_process_input(false)
		level_completed = true
		B4DFramework.set_game_data({"checkpoint" : Vector3.ZERO})
		jump_animation()
#		yield(get_tree().create_timer(3),"timeout")
		current_level.emit_signal("level_completed")
	
	## long stairs right
	if area.is_in_group("long_right"):
		if direction == Vector3.RIGHT:
			side_cam_scroll(area,1)
	## long stairs left
	if area.is_in_group("long_left"):
		if direction == Vector3.LEFT:
			side_cam_scroll(area,-1)

func side_cam_scroll(area : Area , direction : int):
	var long_stair_obj = area.get_parent()
	if not long_stair_obj.is_triggered():
		long_stair_obj.block_way()
		globalGame.emit_signal("on_long_stair",direction)


func jump_animation():
	var timer = Timer.new()
	timer.wait_time = 0.3
	timer.one_shot = false
	timer.connect("timeout",self,"jump")
	add_child(timer)
	timer.start()

## Check if the ball is near a side edge 
## switch direction when the player jumps near a side
func is_side_jump():
	if left_ray.is_colliding() and direction == Vector3.RIGHT:
		switch_direction()
	if right_ray.is_colliding() and direction == Vector3.LEFT:
		switch_direction()

## change the camera focus on the player
func focus_on_player():
	if not level_completed:
		globalGame.emit_signal("on_step")

## Called when on_boost star taken and signal is emitted
func on_boost_emitted(safe_stair : Vector3)-> void:
	if forward.is_active():
		yield(forward,"tween_all_completed") # wait for forward to finish
	is_boost_mode = true
	velocity = Vector3()
	gravity = 0
	speed = 0
	ball_rotation = 0 
	var number_of_stairs = abs(safe_stair.z) - abs(translation.z)
	jump(true,number_of_stairs)
	animation.play("boost_shake")
	yield(animation,"animation_finished")
	ball_boost(safe_stair)

func ball_boost(safe_position : Vector3) -> void :
	var safe_stair : Vector3 = safe_position
	var target = abs(safe_stair.z)  - abs(translation.z)
	
	boost.interpolate_property(self,
	"translation",
	translation,
	translation + Vector3(0,target,-target),
	0.10 * target,
	Tween.TRANS_CUBIC,
	Tween.EASE_OUT)
	boost.start()
	get_node("trail").visible = true


func on_boost_completed():
	is_boost_mode = false
	gravity = gravity_reset
	speed = 300
	ball_rotation = -10
	get_node("trail").visible = false


func set_skin(skin : int):
	var ball_material : Material = ball.get_node("Ball").get_surface_material(0)
	ball_material.albedo_texture = globalGame.ball_skins[skin]


func restore_checkpoint() -> void:
	var game_data = B4DFramework.get_game_data()
	if game_data.keys().find("checkpoint") == -1:
		return
		
	if game_data.checkpoint != Vector3.ZERO:
		set_translation(game_data.checkpoint + Vector3(0,3,0))
		latest_height = game_data.checkpoint.y + 0.4
